/*
 * sender.c
 *
 *  Created on: 2013. 10. 16.
 *      Author: jphong
 */

#include "np.h"
#include "packet.h"

typedef enum {
	Wait_for_call,
	Wait_for_ACK,
	Closing
} State;

extern int DEBUG_ON;							// print debug messages if set
static unsigned int	ReTxTimeout = 100;	// default timeout in msec
static char *localhost = "127.0.0.1";		// default receiver hostname
static char *port = "5678";					// default receiver port

//Packet buffers
static Packet 	sndPacket, rcvPacket;


int main( int argc, char **argv )
{
	struct sockaddr_in peer;
	int 	fd, s;
	unsigned char sndSeq;
	Event 	event;
	State 	state;
	Packet	*sndpkt = &sndPacket;
	Packet *rcvpkt = &rcvPacket;
	char	*data = &(sndPacket.data[0]);
	char	*hostname, *service;
	int 	opt;

	// Command line option processing
	while ( (opt = getopt(argc, argv, "dt:")) != -1) {
		switch (opt) {
		case 'd':
			DEBUG_ON = 1; 	// Debug mode
			break;
		case 't':
			ReTxTimeout = atoi(optarg);
			break;
		default:
			error(1, 0, "Usage: %s [-d] [-t timeout] [<receiver name or IP address> <port number>]\n", argv[0] );
		}
	}
	if (optind == argc-2) {
		hostname = argv[optind];
		service = argv[optind+1];
	} else if (optind == argc) {
		hostname = localhost;
		service = port;
	} else
		error(1, 0, "Usage: %s [-d] [-t timeout] [<receiver name or IP address> <port number>]\n", argv[0] );
	debug("To %s:%s\tTimeout = %dmsec\tDebugMode = %s\n", hostname, service, ReTxTimeout,
			DEBUG_ON ? "on" : "off");

	set_address( hostname, service, &peer, "udp" ); // Set peer's IP and port

	if ( (s = socket( AF_INET, SOCK_DGRAM, 0 )) < 0)
		error(1, errno, "socket creation failed\n" );

	fd = fileno(stdin);

	// Init conditiion
	sndSeq = 0;
	state = Wait_for_call;

	// FSM
	for (;;) {
		int 	datalen, rcvlen;

		event = getEvent(fd, s);
		// Actions taken by State and Event
		switch (state) {
		// Wait_for_call state
		case Wait_for_call:
			switch (event) {
			case RDT_SEND:
				if ( fgets( data, MAX_DATA, stdin) != NULL ) {  // Get data from above

					// 보낼 data가 있으니 패킷을 만들고 udt_send 날리고 + 타이머 시작

					sndpkt = make_pkt(DATA, sndSeq, &sndPacket, sizeof(data));  // * 안뎀

					udt_send(s, sndpkt, (struct sockaddr *)&peer,	sizeof(*sndpkt));  // s  +&peer ->  *sndpkt

					//sndSeq = sndSeq+sizeof(data);

					sndSeq = (sndSeq+1) % 2;           //0과 1을 반복하게?

					start_timer(ReTxTimeout);

					continue;

				} else {

					//EoF를 만나면 END pkt를 날리고 closing 대기하자

					sndpkt = make_pkt(END, sndSeq, &sndPacket, 0);           //수정 &data
					udt_send(s, sndpkt, (struct sockaddr *) &peer, sizeof(*sndpkt));    // fd -> s  +&peer ->  *sndpkt

					sndSeq = (sndSeq+1) % 2;

					start_timer(ReTxTimeout);

				}
			default:

				continue;				// ignore the event
			}

		//Wait_for_ACK state
		case Wait_for_ACK:
			switch (event) {
			case RDT_RCV:

				 // Corrupted packet
				 // 오류있는 pkt 검출 - data날린것이 잘못되었는지, 혹은 응답ACK가 잘못된건지 알수없다
			     // 그러나 2가지 경우 모두 그냥 재전송하면 해결된다.

				if ( isCorrupt(rcvpkt) ) {
					debug("Corrupted packet received -> ReTx Data \n");

					//udt_send(s, sndpkt, (struct sockadd *) &peer,	sizeof(peer));
					udt_send(s, sndpkt, (struct sockaddr *)&peer, sizeof(*sndpkt));  // Retx DATA  , s  // +& pper  +&peer ->  *sndpkt
					continue;
				}

				// Not corrupted packet
				// ACK packet 잘 받았다 - 그러면 타이머를 종료하자.

				if ( isType(rcvpkt, ACK, sndSeq) ) {

					stop_timer();  // 타이머 끄고   State 전이

					state = Wait_for_call;
					continue;;

				}

				continue;


			case TIMEOUT:
				// timeout까지 ack가 오지않으면 재전송한다

				debug("Timeout -> ReTx Data \n");

				stop_timer();  // add

				udt_send(s, sndpkt, (struct sockaddr *)&peer, sizeof(*sndpkt));  // Retx DATA, s // +& pper  +&peer ->  *sndpkt

				start_timer(ReTxTimeout);


				continue;
			default:
				continue;
			}

		// wait an ACK. Sender shall stop when anything hear from receiver
		// 종료대기 상태, EoF(END)에 대한 ACK를 받으면 종료

		case Closing:
			switch (event) {
			case RDT_RCV:

				if ( isCorrupt(rcvpkt) ) {
					debug("Corrupted packet received -> ReTx Data \n");

					stop_timer();

					udt_send(s, sndpkt, (struct sockaddr *)&peer, sizeof(*sndpkt));  // Retx DATA // +& pper  +&peer ->  *sndpkt


					start_timer(ReTxTimeout);

					continue;
				}

				// Not corrupted packet
				if ( isType(rcvpkt, ACK, sndSeq) ) { // ACK packet 잘 받았다

					stop_timer();  // 타이머 끄고 종료. 안꺼도 상관없고.. EoF에서 아예 안켜도 되고..

					break;

					continue;;
				}

			case TIMEOUT:

				stop_timer();

				sndpkt = make_pkt(END, sndSeq, &sndPacket, 0);  //*data -> &sndPacket

				udt_send(s, sndpkt, (struct sockaddr *) &peer, sizeof(*sndpkt));   //수정 +r  fd->s   +&peer ->  *sndpkt
				start_timer(ReTxTimeout);
				continue;

			default:
				continue;
			}	// End of inner switch
		}	// End of outer switch

	}   // End of for loop

}	// End of main

